﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Input;
using System.Drawing;
using OpenTK.Graphics;
using Common;

namespace T16_ShadowMaps {
    class T16_ShadowMapsGameWindow : GameWindow {
        public T16_ShadowMapsGameWindow() {
            this.Load += T16_ShadowMapsGameWindow_Load;
            this.Resize += T16_ShadowMapsGameWindow_Resize;
            this.RenderFrame += T16_ShadowMapsGameWindow_RenderFrame;
            this.Unload += T16_ShadowMapsGameWindow_Unload;
            this.Width = 1024;
            this.Height = 768;
            this.Title = "T16_ShadowMapsGameWindow";

        }


        private void T16_ShadowMapsGameWindow_RenderFrame(object sender, FrameEventArgs e) {
            //GL.ClearColor(.1f, .1f, .99f, .9f);
            //GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            Render();

            SwapBuffers();
            ProcessEvents();
        }

        Vao vao;
        Shader depthShader, quadShader, shadowMappingShader;
        ModelMesh mesh;
        Vbo<Vector3> vertexBuffer, normalBuffer;
        Vbo<Vector2> uvBuffer;
        Vbo<ushort> indexBuffer;
        VertexAttribManager attribs = new VertexAttribManager();
        VertexAttribManager attribsOne = new VertexAttribManager();
        VertexAttribManager quadAttribs = new VertexAttribManager();
        Texture2D texture;
        int frameBufferName;
        int depthTexture;
        float[] quadVertexBufferData = {
        -1.0f, -1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,
        -1.0f,  1.0f, 0.0f,
        -1.0f,  1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,
         1.0f,  1.0f, 0.0f,
    };
        Vbo<float> quadBuffer;
        private void T16_ShadowMapsGameWindow_Load(object sender, EventArgs e) {

            VSync = VSyncMode.Off;
            GL.Enable(EnableCap.DepthTest);
            GL.DepthFunc(DepthFunction.Less);
            GL.Enable(EnableCap.CullFace);

            GL.ClearColor(0, 0, .4f, 0);
            vao = new Vao();

            depthShader = new Shader("DepthRTT.vertexshader", "DepthRTT.fragmentshader");
            mesh = MeshLoader.LoadObjIndexed("room_thickwalls.obj");

            vertexBuffer = new Vbo<Vector3>(mesh.Vertices);
            uvBuffer = new Vbo<Vector2>(mesh.Uvs);
            normalBuffer = new Vbo<Vector3>(mesh.Normals);
            indexBuffer = new Vbo<ushort>(BufferTarget.ElementArrayBuffer, BufferUsageHint.StaticDraw, mesh.Indices);

            attribsOne.AddAttrib(3, vertexBuffer);

            attribs.AddAttrib(3, vertexBuffer);
            attribs.AddAttrib(2, uvBuffer);
            attribs.AddAttrib(3, normalBuffer);


            texture = Texture2D.Load("uvmap.dds");

            frameBufferName = GL.GenFramebuffer();
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, frameBufferName);

            depthTexture = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, depthTexture);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.DepthComponent16, 1024, 1024, 0, PixelFormat.DepthComponent, PixelType.Float, IntPtr.Zero);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureCompareFunc, (int)All.Lequal);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureCompareMode, (int)TextureCompareMode.CompareRToTexture);

            GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, depthTexture, 0);

            GL.DrawBuffer(DrawBufferMode.None);

            if (GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer) != FramebufferErrorCode.FramebufferComplete){
                throw new Exception("framebuffer not complete!");
            }

            quadBuffer = new Vbo<float>(quadVertexBufferData);
            quadAttribs.AddAttrib(3, quadBuffer);

            quadShader = new Shader("Passthrough.vertexshader", "SimpleTexture.fragmentshader");

            shadowMappingShader = new Shader("ShadowMapping.vertexshader", "ShadowMapping.fragmentshader");
        }

        private void Render() {
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, frameBufferName);
            GL.Viewport(0, 0, 1024, 1024);
            GL.Enable(EnableCap.CullFace);
            GL.CullFace(CullFaceMode.Back);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            depthShader.Use();
            Vector3 lightInvDir = new Vector3(.5f, 2, 2);
            Matrix4 depthProjectionMatrix = Matrix4.CreateOrthographicOffCenter(-10, 10, -10, 10, -10, 20);
            var depthViewMatrix = Matrix4.LookAt(lightInvDir, new Vector3(0, 0, 0), new Vector3(0, 1, 0));

            var depthModelMatrix = Matrix4.Identity;
            var depthMvp = depthModelMatrix * depthViewMatrix * depthProjectionMatrix;
            depthShader.SetMatrix4("depthMVP", depthMvp);

            attribsOne.Enable();
            indexBuffer.Bind();
            GL.DrawElements(BeginMode.Triangles, mesh.Indices.Length, DrawElementsType.UnsignedShort, 0);
            attribsOne.Disable();

            //render to screen
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
            GL.Viewport(0, 0, this.Width, this.Height);
            GL.Enable(EnableCap.CullFace);
            GL.CullFace(CullFaceMode.Back);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            shadowMappingShader.Use();
            
            InputControls.computeMatricesFromInput(this);
            var projection = InputControls.ProjectionMatrix;
            var view = InputControls.ViewMatrix;
            var model = Matrix4.Identity;
            var mvp = model * view * projection;
            Matrix4 biasMatrix = new Matrix4(0.5f, 0.0f, 0.0f, 0.0f,
            0.0f, 0.5f, 0.0f, 0.0f,
            0.0f, 0.0f, 0.5f, 0.0f,
            0.5f, 0.5f, 0.5f, 1.0f);

            Matrix4 depthBiasMvp = depthMvp * biasMatrix;

            shadowMappingShader.SetMatrix4("MVP", ref mvp);
            shadowMappingShader.SetMatrix4("M", ref model);
            shadowMappingShader.SetMatrix4("V", ref view);
            shadowMappingShader.SetMatrix4("DepthBiasMVP", ref depthBiasMvp);

            shadowMappingShader.SetVector3("LightInvDirection_worldspace", lightInvDir);

            texture.Active(TextureUnit.Texture0, shadowMappingShader, "myTextureSampler");

            GL.ActiveTexture(TextureUnit.Texture1);
            GL.BindTexture(TextureTarget.Texture2D, depthTexture);
            shadowMappingShader.SetInt("shadowMap", 1);

            attribs.Enable();
            indexBuffer.Bind();
            GL.DrawElements(BeginMode.Triangles, mesh.Indices.Length, DrawElementsType.UnsignedShort, 0);
            attribs.Disable();


            // Optionally render the shadowmap (for debug only)

            // Render only on a corner of the window (or we we won't see the real rendering...)
            GL.Viewport(0, 0, 256, 256);
            quadShader.Use();
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, depthTexture);
            quadShader.SetInt("myTexture", 0);

            quadAttribs.Enable();
            GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
            quadAttribs.Disable();



        }

        private void T16_ShadowMapsGameWindow_Resize(object sender, EventArgs e) {
            GL.Viewport(0, 0, Width, Height);
        }



        private void T16_ShadowMapsGameWindow_Unload(object sender, EventArgs e) {
            GlDisposableObjectBase.DisposeObjects(vao, depthShader,quadShader,shadowMappingShader,texture,vertexBuffer,uvBuffer,normalBuffer,indexBuffer,quadBuffer,mesh);
        }

    }
}
